package com.wmada.toolkit.common;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 类型转换工具类
 * <p>
 * The type Convert utils.
 */
@SuppressWarnings("unused")
public class ConvertUtils {

    private static final Logger logger = LoggerFactory.getLogger(ConvertUtils.class);

    /**
     * Parse int integer.
     *
     * @param str the str
     * @return the integer
     */
    public static Integer parseInt(String str) {
        return parseInt(str, null);
    }

    /**
     * Parse int integer.
     *
     * @param str          the str
     * @param defaultValue the default value
     * @return the integer
     */
    public static Integer parseInt(String str, Integer defaultValue) {
        try {
            return Integer.parseInt(str);
        } catch (Exception e) {
            Double doubleValue = parseDouble(str, null);
            if (doubleValue != null) {
                return doubleValue.intValue();
            } else {
                return defaultValue;
            }
        }
    }

    /**
     * Parse int integer.
     *
     * @param num          the num
     * @param defaultValue the default value
     * @return the integer
     */
    public static Integer parseInt(Object num, Integer defaultValue) {
        return parseInt(parseString(num), defaultValue);
    }

    /**
     * Parse int integer.
     *
     * @param num the num
     * @return the integer
     */
    public static Integer parseInt(Object num) {
        return parseInt(parseString(num));
    }

    /**
     * Parse long long.
     *
     * @param str the str
     * @return the long
     */
    public static Long parseLong(String str) {
        return parseLong(str, null);
    }

    /**
     * Parse long long.
     *
     * @param str          the str
     * @param defaultValue the default value
     * @return the long
     */
    public static Long parseLong(String str, Long defaultValue) {
        try {
            return Long.parseLong(str);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Parse long long.
     *
     * @param num the num
     * @return the long
     */
    public static Long parseLong(Object num) {
        return parseLong(parseString(num));
    }

    public static Character parseChar(Object obj, Character defaultValue) {
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Character) {
            return (Character) obj;
        }
        if (TypeUtils.isInteger(obj)) {
            Integer value = ConvertUtils.parseInt(obj);
            return value != null ? (char) value.intValue() : defaultValue;
        }
        return defaultValue;
    }

    public static Character parseChar(Object obj) {
        return parseChar(obj, null);
    }

    /**
     * Parse double double.
     *
     * @param str the str
     * @return the double
     */
    public static Double parseDouble(String str) {
        return parseDouble(str, null);
    }

    /**
     * Parse double double.
     *
     * @param str          the str
     * @param defaultValue the default value
     * @return the double
     */
    public static Double parseDouble(String str, Double defaultValue) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Parse double double.
     *
     * @param num the num
     * @return the double
     */
    public static Double parseDouble(Object num) {
        return parseDouble(parseString(num));
    }

    /**
     * Parse float float.
     *
     * @param str the str
     * @return the float
     */
    public static Float parseFloat(String str) {
        return parseFloat(str, null);
    }

    /**
     * Parse float float.
     *
     * @param str          the str
     * @param defaultValue the default value
     * @return the float
     */
    public static Float parseFloat(String str, Float defaultValue) {
        try {
            return Float.parseFloat(str);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Parse float float.
     *
     * @param num the num
     * @return the float
     */
    public static Float parseFloat(Object num) {
        return parseFloat(parseString(num));
    }

    /**
     * Parse boolean boolean.
     *
     * @param str the str
     * @return the boolean
     */
    public static Boolean parseBoolean(String str) {
        return parseBoolean(str, null);
    }

    /**
     * Parse boolean boolean.
     *
     * @param str          the str
     * @param defaultValue the default value
     * @return the boolean
     */
    public static Boolean parseBoolean(String str, Boolean defaultValue) {
        try {
            return Boolean.parseBoolean(str);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * Parse boolean boolean.
     *
     * @param num the num
     * @return the boolean
     */
    public static Boolean parseBoolean(Object num) {
        return parseBoolean(parseString(num));
    }

    /**
     * Parse boolean boolean.
     *
     * @param num the num
     * @return the boolean
     */
    public static Boolean parseBoolean(Object num, Boolean defaultValue) {
        return parseBoolean(parseString(num), defaultValue);
    }

    /**
     * Parse string string.
     *
     * @param num the num
     * @return the string
     */
    public static String parseString(Object num) {
        try {
            if (num == null) {
                return "";
            }
            return String.valueOf(num);
        } catch (Exception e) {
            return "";
        }
    }

    public static Date parseDate(String str) {
        return parseDate(str, null);
    }

    private static final String PATTERN_STANDARD_STR    = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}";
    private static final String PATTERN_STANDARD_US_STR = "\\w{3} \\w{3} \\d{2} \\d{2}:\\d{2}:\\d{2} \\w{8} \\d{4}";

    private static final Pattern PATTERN_STANDARD    = Pattern.compile(PATTERN_STANDARD_STR);
    private static final Pattern PATTERN_STANDARD_US = Pattern.compile(PATTERN_STANDARD_US_STR);

    public static Date parseDate(String str, Date defaultValue) {
        if (StringUtils.isBlank(str)) {
            return defaultValue;
        }

        if (PATTERN_STANDARD.matcher(str).matches()) {
            return DateFormat.DATETIME_FORMAT.parse(str, defaultValue);
        } else if (PATTERN_STANDARD_US.matcher(str).matches()) {
            return DateFormat.DATETIME_FORMAT_US.parse(str, defaultValue);
        } else {
            return defaultValue;
        }
    }

    /**
     * Fill blank string.
     *
     * @param text     the text
     * @param totalLen the total len
     * @return the string
     */
    public static String fillBlank(String text, int totalLen) {
        return fillText(text, totalLen, ' ', true);
    }

    /**
     * Fill blank string.
     *
     * @param text     the text
     * @param totalLen the total len
     * @param fillLeft the fill left
     * @return the string
     */
    public static String fillBlank(String text, int totalLen, boolean fillLeft) {
        return fillText(text, totalLen, ' ', fillLeft);
    }

    /**
     * Fill zero string.
     *
     * @param text     the text
     * @param totalLen the total len
     * @return the string
     */
    public static String fillZero(String text, int totalLen) {
        return fillText(text, totalLen, '0', true);
    }

    /**
     * Fill zero string.
     *
     * @param text     the text
     * @param totalLen the total len
     * @param fillLeft the fill left
     * @return the string
     */
    public static String fillZero(String text, int totalLen, boolean fillLeft) {
        return fillText(text, totalLen, '0', fillLeft);
    }

    /**
     * Fill text string.
     *
     * @param text     the text
     * @param totalLen the total len
     * @param fillChar the fill char
     * @return the string
     */
    public static String fillText(String text, int totalLen, char fillChar) {
        return fillText(text, totalLen, fillChar, true);
    }

    /**
     * Fill text string.
     *
     * @param text     the text
     * @param totalLen the total len
     * @param fillChar the fill char
     * @param fillLeft the fill left
     * @return the string
     */
    public static String fillText(String text, int totalLen, char fillChar, boolean fillLeft) {
        if (totalLen <= text.length()) {
            return text;
        }
        StringBuilder fillingTextBuf = new StringBuilder();
        for (int i = text.length(); i < totalLen; i++) {
            fillingTextBuf.append(fillChar);
        }
        if (fillLeft) {
            return fillingTextBuf.append(text).toString();
        } else {
            return text.concat(fillingTextBuf.toString());
        }
    }

    public static Class<?> forName(String name) {
        return TypeUtils.BASIC_CLASS_NAMES.get(name);
    }

    @SuppressWarnings("unchecked")
    public static <T> T convertBasic(Object value, Class<T> tClass) {
        try {
            if (tClass == Byte.class || tClass == byte.class) {
                return (T) Byte.valueOf(value.toString());
            } else if (tClass == Short.class || tClass == short.class) {
                return (T) (Short) Short.parseShort(value.toString().replace(",", ""));
            } else if (tClass == Integer.class || tClass == int.class) {
                return (T) parseInt(value.toString().replace(",", ""));
            } else if (tClass == Long.class || tClass == long.class) {
                return (T) parseLong(value);
            } else if (tClass == Character.class || tClass == char.class) {
                return (T) parseChar(value);
            } else if (tClass == Float.class || tClass == float.class) {
                Float fValue;
                if (value.toString().endsWith("%")) {
                    fValue = parseFloat(value.toString().replace("%", "")) / 100;
                } else {
                    fValue = parseFloat(value);
                }
                return (T) fValue;
            } else if (tClass == Double.class || tClass == double.class) {
                Double dValue;
                if (value.toString().endsWith("%")) {
                    dValue = parseDouble(value.toString().replace("%", "")) / 100;
                } else {
                    dValue = parseDouble(value);
                }
                return (T) dValue;
            } else if (tClass == Boolean.class || tClass == boolean.class) {
                String strValue = value.toString().trim().toLowerCase();
                if (strValue.length() == 0) {
                    return (T) Boolean.FALSE;
                } else if (TypeUtils.isNumber(strValue)) {
                    return (T) (0 != parseInt(strValue) ? Boolean.TRUE : Boolean.FALSE);
                } else {
                    return (T) (!"false".equals(strValue) ? Boolean.TRUE : Boolean.FALSE);
                }
            } else if (tClass == String.class) {
                if (value instanceof Date) {
                    return (T) DateFormat.DATETIME_FORMAT.format((Date) value);
                } else {
                    return (T) value.toString();
                }
            } else if (tClass == Date.class || tClass == Timestamp.class || tClass == java.sql.Date.class || tClass == Time.class) {
                return (T) parseDate(value.toString());
            } else if (tClass == BigInteger.class) {
                return (T) BigInteger.valueOf(parseLong(value));
            } else if (tClass == BigDecimal.class) {
                return (T) BigDecimal.valueOf(parseDouble(value));
            } else if (tClass.isEnum()) {
                return convertEnum(value, tClass);
            }
        } catch (Exception e) {
            logger.error("#对象转换类型出错！value: " + value + ", class: " + tClass.getName(), e);
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static <T> T convertEnum(Object value, Class<T> tClass) {
        try {
            return (T) tClass.getDeclaredMethod("valueOf", Object.class).invoke(null, value);
        } catch (Exception e) {
            if (!(e instanceof NoSuchMethodException)) {
                logger.debug("#对象转换类型出错！value: " + value + ", class: " + tClass.getName() + ". 无法使用自定义valueOf方法", e);
            }
        }
        try {
            return (T) tClass.getDeclaredMethod("valueOf", String.class).invoke(null, value.toString());
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            logger.error("#对象转换类型出错！value: " + value + ", class: " + tClass.getName() + ". 无法使用默认valueOf方法", e);
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Convert type t.
     *
     * @param <T>    the type parameter
     * @param value  the value
     * @param tClass the clazz
     * @return the t
     * @throws ClassCastException the class cast exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T convertType(Object value, Class<T> tClass) throws ClassCastException {
        if (value == null || tClass == null) {
            return (T) value;
        }
        if (tClass.isInstance(value) || value.getClass().equals(tClass)) {
            return (T) value;
        }
        try {
            if (tClass.isEnum()) {
                return convertEnum(value, tClass);
            } else if (tClass == String.class) {
                if (value instanceof Date) {
                    return (T) DateFormat.DATETIME_FORMAT.format((Date) value);
                } else {
                    return (T) value.toString();
                }
            } else if (tClass == Boolean.class) {
                String lValue = value.toString().trim().toLowerCase();
                if (TypeUtils.isNumber(lValue)) {
                    return (T) (parseInt(lValue) != 0 ? Boolean.TRUE : Boolean.FALSE);
                } else {
                    return (T) (lValue.equals("true") ? Boolean.TRUE : Boolean.FALSE);
                }
            } else if (tClass == Date.class || tClass == Timestamp.class || tClass == java.sql.Date.class || tClass == Time.class) {
                return (T) parseDate(value.toString());
            } else if (tClass == Integer.class || tClass == int.class) {
                return (T) parseInt(value.toString().replace(",", ""));
            } else if (tClass == Long.class || tClass == long.class) {
                return (T) parseLong(value);
            } else if (tClass == Double.class || tClass == double.class) {
                return (T) parseDouble(value);
            } else if (tClass == Float.class || tClass == float.class) {
                Float fValue;
                if (value.toString().contains("%")) {
                    fValue = BigDecimal.valueOf(parseFloat(value.toString().replace("%", "")) / 100).setScale(4, BigDecimal.ROUND_HALF_UP).floatValue();
                } else {
                    fValue = parseFloat(value);
                }
                return (T) fValue;
            } else if (tClass == Set.class || Set.class.isAssignableFrom(tClass)) {
                return (T) (new LinkedHashSet<Object>() {{
                    add(value);
                }});
            } else if (tClass == List.class || List.class.isAssignableFrom(tClass)) {
                return (T) (new LinkedList<Object>() {{
                    add(value);
                }});
            } else if (tClass == Collection.class || Collection.class.isAssignableFrom(tClass)) {
                return (T) (new LinkedList<Object>() {{
                    add(value);
                }});
            }
        } catch (Exception e) {
            logger.error("#对象转换类型出错！value: " + value + ", class: " + tClass.getName(), e);
            return (T) value;
        }
        try {
            Constructor<?>[] constructors = tClass.getConstructors();
            for (Constructor<?> constructor : constructors) {
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                if (parameterTypes.length == 1 && parameterTypes[0].isInstance(value)) {
                    return (T) constructor.newInstance(value);
                }
            }
        } catch (Exception e) {
            logger.error("#对象转换类型出错: 无法识别的类型! value: " + value + ", class: " + tClass.getName());
            return (T) value;
        }
        logger.error("#对象转换类型出错: 无法识别的类型! value: " + value + ", class: " + tClass.getName());
        return (T) value;
    }

    @SuppressWarnings({"unchecked", "TryWithIdenticalCatches", "rawtypes"})
    public static <T> T clone(T o) {
        try {
            if (o == null) {
                return null;
            } else if (o instanceof Collection) {
                Collection arr = (Collection) o.getClass().newInstance();
                for (Object el : (Collection) o) {
                    arr.add(clone(el));
                }
                return (T) arr;
            } else if (o instanceof Map) {
                Map map = (Map) o.getClass().newInstance();
                for (Map.Entry el : (Set<Map.Entry>) ((Map) o).entrySet()) {
                    map.put(el.getKey(), clone(el.getValue()));
                }
                return (T) map;
            } else {
                try {
                    Method func = o.getClass().getMethod("clone");
                    if (func.isAccessible()) {
                        return (T) func.invoke(o);
                    } else {
                        return o;
                    }
                } catch (NoSuchMethodException e) {
                    return o;
                } catch (InvocationTargetException e) {
                    return o;
                } catch (IllegalAccessException e) {
                    return o;
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        return o;
    }

}
